pub mod error;
mod middleware;
mod routers;
use log::LevelFilter;
use read_config::Config;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use std::sync::OnceLock;
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};

static CONFIG: OnceLock<Config> = OnceLock::new();

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cmd_args = std::env::args().collect::<Vec<String>>();
    if cmd_args.len() != 2 {
        return Err(anyhow::anyhow!("丢失配置文件参数！示例:web_back config"));
    }
    let config_path = &cmd_args[1];
    // 读取配置文件
    let config = read_config::Config::read_config(&config_path)?;
    let config = CONFIG.get_or_init(|| config);
    // 终端层
    let terminal_layer = tracing_subscriber::fmt::layer()
        .pretty()
        .with_line_number(true)
        .with_writer(std::io::stderr);
    // 文件层
    let log_path = &config.log.path;
    let project_name = &config.app.name;
    let file_appender = tracing_appender::rolling::daily(&log_path, project_name);
    let (non_blocking_appender, _guard) = tracing_appender::non_blocking(file_appender);
    let env_filter = tracing_subscriber::filter::LevelFilter::from_level(Level::INFO);
    let file_layer = tracing_subscriber::fmt::layer()
        .pretty()
        .with_ansi(false)
        .with_writer(non_blocking_appender)
        .with_line_number(true)
        .with_filter(env_filter);
    // 注册
    tracing_subscriber::Registry::default()
        .with(tracing_error::ErrorLayer::default())
        .with(terminal_layer)
        .with(file_layer)
        .init();
    color_eyre::install().expect("color_eyre install failed");

    // 连接数据库
    let db_holder = db_connection().await;
    let routers = routers::router_new(db_holder).await;
    let addr = &config.server.address;
    let port = config.server.port;
    let listener = tokio::net::TcpListener::bind(&format!("{}:{}", addr, port)).await?;

    tracing::info!("listening on {}", addr);
    axum::serve(listener, routers.into_make_service())
        .with_graceful_shutdown(shutdown_signal())
        .await?;
    Ok(())
}

async fn db_connection() -> DatabaseConnection {
    let config = CONFIG.get().unwrap();
    let database = &config.database;
    let database_url = &database.url;
    let max_connections = database.max_connections;
    let min_connections = database.min_connections;
    let connect_timeout = database.connect_timeout;
    let idle_timeout = database.idle_timeout;
    let max_lifetime = database.max_lifetime;
    let sqlx_logging = database.sqlx_logging;
    let sqlx_logging_level = database.sqlx_logging_level;
    let mut opt = ConnectOptions::new(database_url.clone());
    opt.max_connections(max_connections)
        .min_connections(min_connections)
        .connect_timeout(std::time::Duration::from_secs(connect_timeout as u64))
        .idle_timeout(std::time::Duration::from_secs(idle_timeout as u64))
        .max_lifetime(std::time::Duration::from_secs(max_lifetime as u64))
        .sqlx_logging(sqlx_logging)
        .sqlx_logging_level(match sqlx_logging_level {
            read_config::Level::Off => LevelFilter::Off,
            read_config::Level::Error => LevelFilter::Error,
            read_config::Level::Warn => LevelFilter::Warn,
            read_config::Level::Info => LevelFilter::Info,
            read_config::Level::Debug => LevelFilter::Debug,
            read_config::Level::Trace => LevelFilter::Trace,
        });

    let db = Database::connect(opt).await;
    if let Err(err) = db {
        panic!("{}", err.to_string());
    }
    db.unwrap()
}

async fn shutdown_signal() {
    let ctrl_c = async {
        tokio::signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handle");
    };

    #[cfg(unix)]
    let terminal = async {
        tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminal = std::future::pending::<()>();
    tokio::select! {
        _ = ctrl_c => {},
        _ = terminal => {},
    }
}
